home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d12 / ddj0190.arc / NOLAN.LST < prev    next >
File List  |  1989-12-19  |  15KB  |  378 lines

  1. _REAL-TIME DATA ACQUISITION USING DMA_
  2. by Tom Nolan
  3.  
  4. [LISTING ONE]
  5.  
  6. /*--------------------------------------------------------------------------*/
  7. /* dma.c -- subroutines for dma data acquisition
  8.  * The calling routine must declare the variables in the "extern" list
  9.  * below, and the reset_irq() function. Communication from the main
  10.  * program to the subroutines is mostly through these global variables.
  11.  * The calling routine must give values to dma_chan, dma_irq and buf_size,
  12.  * then call alloc_dma_buf(), dma_setup(), and start_dma(). As each
  13.  * dma buffer fills up, the interrupt service routine calls start_dma() on
  14.  * the next buffer. The calling routine can wait for buf_index to
  15.  * change, then process data pointed to by curr_buf. Cleanup is done
  16.  * by dma_finish(), which is called automatically when the program exits.
  17.  * Compiler: Microsoft C Version 5.0
  18.  *             Set /Gs switch to remove stack probes (a necessity for any
  19.  *             function called at interrupt state!) 
  20.  * Tom Nolan - 8/7/89
  21.  */
  22.  
  23. #include <dos.h>
  24.  
  25. /* DMA Register Definitions */
  26.  
  27. #define DMA0_BASE    0x00    /* address of dma controller (chan 0-3) */
  28. #define DMA1_BASE    0xC0    /* address of dma controller (chan 4-7) */
  29.  
  30. /* Interrupt Controller Definitions */
  31.  
  32. #define INTA00        0x20    /* base address of int ctrlr */
  33. #define INTA01        0x21    /* address of int ctrlr 2nd reg */
  34. #define EOI            0x20     /* code for non-specific end-of-int */
  35.     
  36.  
  37. /* External Variables */
  38.  
  39. extern char far *dma_buffers[];    /* array containing buffer addresses */
  40. extern int      buf_index;    /* index of current buffer in array */
  41. extern char far *curr_buf;    /* pointer to just-filled buffer */
  42. extern unsigned buf_size;    /* size of buffers in bytes */
  43. extern int      lost_buffers;    /* count of buffers unable to be written */
  44. extern int      dma_irq;    /* h/w interrupt when dma complete (0-7) */
  45. extern int      dma_chan;    /* channel number for dma operation */
  46. extern int      file_handle;    /* handle of archive file (0=no file) */
  47. extern void     reset_irq();    /* function to reset interrupt request */
  48.  
  49.  
  50. /* local variables - placed in static storage to
  51.  * avoid excessive stack usage in interrupt routines */
  52.  
  53. static union  REGS  r;        /* general registers */
  54. static struct SREGS s;        /* segment registers */
  55. static int sel;            /* dma channel select bits */
  56. static int basereg;        /* dma controller base address register */
  57. static int cntreg;        /* dma controller count register */
  58. static int maskreg;        /* dma controller mask register */
  59. static int modereg;        /* dma controller mode register */
  60. static int pagereg;        /* dma page address register */
  61. static int page_tbl[] =        /* table of page register addresses */
  62.         { 0x87, 0x83, 0x81, 0x82,    /* for dma channels 0, 1, 2, 3 */
  63.           0x8f, 0x8b, 0x89, 0x8a };  /*                  4, 5, 6, 7 */
  64. char far *dos_crit_addr;    /* address of DOS critical section flag */
  65. static void             /* space for saved int vector contents */
  66.     (interrupt far *dma_int_save)();
  67.  
  68. /* macros for extracting bytes from 20-bit addresses */
  69.  
  70. #define LSB(x)  *((unsigned char *) &x)
  71. #define MSB(x)  *(((unsigned char *) &x) + 1)
  72. #define PAGE(x) *(((unsigned char *) &x) + 2)
  73.  
  74. /* Function Prototypes */
  75.  
  76. void dma_setup(void);
  77. void dma_finish(void);
  78. int alloc_dma_buf(void);
  79. void start_dma(char far *, unsigned);
  80. void interrupt far dma_isr(void);
  81. int write_buf();
  82.  
  83. /*--------------------------------------------------------------------------*/
  84. int alloc_dma_buf()        /* allocate a pair of dma buffers */
  85. {
  86.     unsigned buf;        /* temp variables for various */
  87.     unsigned max;        /*   paragraph addreses       */
  88.     unsigned seg;
  89.     unsigned size;        /* buffer size in paragraphs */
  90.  
  91.     /* This routine allocates a pair of buffers that can be
  92.      * filled by dma. The buffers are guaranteed to be 
  93.      * aligned so that they do not cross physical page boundaries.
  94.      * Before calling this routine, set the value of buf_size to 
  95.      * the required number of bytes in each buffer. The maximum
  96.      * buffer size is 64K bytes, which can be allocated
  97.      * by specifying a buf_size of zero. The byte count is converted 
  98.      * to paragraphs, which are the units the DOS memory allocation 
  99.      * functions work with. Buffer addresses returned in dma_buffers[0] 
  100.      * and dma_buffers[1]. Return value is zero if allocation succeeded, 
  101.      * non-zero (an MS-DOS error code) otherwise.
  102.      */
  103.  
  104.     size = (buf_size == 0) ? /* convert bytes to paragraphs */
  105.             0x1000 : buf_size >> 4; /* ..by dividing by 16 */
  106.     _dos_allocmem(0xffff, &max);    /* get max paragraphs from dos */
  107.     _dos_allocmem(max, &seg);    /* now grab it all */
  108.  
  109.     buf = seg;        /* initial attempt at buffer segment */
  110.     if( ((buf + size - 1) & 0xf000)     /* if buffer crosses  */
  111.              != (buf & 0xf000) ) /*  phys page bdry    */
  112.         buf = (buf & 0xf000) + 0x1000; /*...adjust to next phys page */
  113.  
  114.     dma_buffers[0] = (char far *)    /* convert buffer segment  */
  115.              ((long) buf << 16); /*... to far pointer for return */
  116.  
  117.     buf += size;            /* initial attempt at next buffer */
  118.     if( ((buf + size - 1) & 0xf000) 
  119.             != (buf & 0xf000) )
  120.                buf = (buf & 0xf000) + 0x1000; /* adjust if crosses page bdry */
  121.  
  122.     dma_buffers[1] = (char far *)    /* return it as a far pointer */
  123.                 ((long) buf << 16);
  124.  
  125.     size = buf + size - seg;    /* compute actual size needed */
  126.     return                 /* free unneeded memory and     */
  127.                _dos_setblock(size, seg, &max); /* return error if not enough */
  128. }
  129.  
  130. /*--------------------------------------------------------------------------*/
  131. void dma_setup()        /* set up for dma operations */
  132. {
  133.     /* Before calling this routine set the following variables:
  134.      *     dma_chan - channel number (hardware dependent)
  135.      *    dma_irq  - interrupt request number 0-7 (hardware dependent)
  136.      */
  137.  
  138.     int intmsk;
  139.  
  140.     sel  = dma_chan & 3;    /* isolate channel select bits */
  141.     pagereg = page_tbl[dma_chan];    /* locate corresponding page reg */
  142.  
  143.     if(dma_chan < 4)    /* setup depends on chan number */
  144.     {
  145.         basereg = DMA0_BASE + sel * 2;    /* standard dma controller */
  146.         cntreg  = basereg + 1;       /* note that this controller  */
  147.         maskreg = DMA0_BASE + 10;    /*     is addressed on byte   */
  148.         modereg = DMA0_BASE + 11;    /*         boundaries         */
  149.     }
  150.     else
  151.     {
  152.              basereg = DMA1_BASE + sel * 4; /* alternate dma ctrlr (AT only) */
  153.              cntreg  = basereg + 2;        /* note that this controller     */
  154.         maskreg = DMA1_BASE + 20;   /*     is addressed on word      */
  155.         modereg = DMA1_BASE + 22;   /*         boundaries            */
  156.     }
  157.     
  158.     r.h.ah = 0x34;            /* dos "get critical flag addr" function */
  159.     intdosx(&r, &r, &s);
  160.     dos_crit_addr = (char far *) /* save its address so it can be tested */
  161.         (((long) s.es << 16) | r.x.bx);    /* ... as a far pointer */
  162.  
  163.     if(dma_irq < 0 || dma_irq > 7)    /* validate interrupt number */
  164.         return;
  165.     dma_int_save =              /* save current contents of dma int vec */
  166.         _dos_getvect(dma_irq + 8);
  167.     _dos_setvect(dma_irq+8, dma_isr); /* set up new int service routine */
  168.  
  169.     intmsk = inp(INTA01);          /* get current interrupt enable mask */
  170.     intmsk &= ~(1 << dma_irq);    /* clear mask bit for dma interrupt */
  171.     outp(INTA01, intmsk);          /* output new mask, enabling interrupt */
  172.     atexit(dma_finish);          /* register exit function */
  173. }
  174.  
  175. /*--------------------------------------------------------------------------*/
  176. static void dma_finish()        /* called via atexit() mechanism */
  177. {
  178.     int intmsk;
  179.  
  180.     intmsk = inp(INTA01);         /* get current interrupt enable mask */
  181.     intmsk |= (1 << dma_irq);    /* set mask bit for dma interrupt */
  182.     outp(INTA01, intmsk);         /* output new mask, disabling interrupt */
  183.  
  184.     _dos_setvect(dma_irq+8, dma_int_save);/* restore old vector contents */
  185. }
  186.  
  187. /*--------------------------------------------------------------------------*/
  188. void start_dma(buf, count)            /* start a dma operation */
  189. char far *buf;                    /* address of buffer to be filled */
  190. unsigned count;                    /* size of buffer in bytes */
  191. {
  192.     int page;
  193.     unsigned long addr =         /* 20-bit address of dma buffer */
  194.             FP_OFF(buf) +
  195.             (long) FP_SEG(buf) << 4;
  196.  
  197.     /* This routine starts a dma operation. It needs to know:
  198.      *     - the address where the dma buffer starts;
  199.      *     - the number of bytes to transfer;
  200.      * The dma buffer address is supplied in segmented, far-pointer
  201.      * form (as returned by alloc_dma_buf()). In this routine it is
  202.      * converted to a 20-bit address by combining the segment and
  203.      * offset. The upper four bits are known as the page number, and
  204.      * are handled separately from the lower 16 bits. The transfer
  205.      * count is decremented by 1 because the dma controller reaches 
  206.      * terminal count when the count rolls over from 0000 to ffff.
  207.      *
  208.      * The dma transfer stops when the channel reaches terminal count.
  209.      * The terminal count signal is turned around in the interface
  210.      * hardware to produce an interrupt when dma is complete.
  211.      *
  212.      * Channels 4-7 are on a separate dma controller, available on 
  213.      * the PC-AT only. They perform 16-bit transfers instead of 8-bit 
  214.      * transfers, and they are addressed in words instead of bytes.
  215.      * This routine handles the addressing requirements based
  216.      * on the channel number.
  217.      *
  218.      * dma_setup() needs to be called before start_dma() in order to
  219.      * assign values to maskreg, modereg, etc.
  220.      */
  221.  
  222.     page = PAGE(addr);        /* extract upper bits of address */
  223.  
  224.     if(dma_chan >= 4)        /* for word-oriented channels... */
  225.     {
  226.         count >>= 1;        /* convert count to words */
  227.         addr  >>= 1;        /* convert address to words */
  228.         page  &=  0x7e;        /* address bit 16 is now in 'addr' */
  229.     }
  230.  
  231.     count--;              /* compute count-1 (xfr stops at ffff) */
  232.     outp(maskreg,     sel | 0x04);    /* set mask bit to disable dma */
  233.     outp(modereg,     sel | 0x44);/* xfr mode (sngl, inc, noinit, write) */
  234.     outp(basereg,     LSB(addr) );    /* output base address lsb */
  235.     outp(basereg,     MSB(addr) );    /* output base address msb */
  236.     outp(pagereg,     page      );/* output page number to page register */
  237.     outp(cntreg,      LSB(count));    /* output count lsb */
  238.     outp(cntreg,      MSB(count));    /* output count msb */
  239.     outp(maskreg,     sel       );    /* clear mask bit, enabling dma */
  240. }
  241.  
  242. /*--------------------------------------------------------------------------*/
  243. static void interrupt far dma_isr()
  244. {
  245.     /* This routine is entered upon completion of a dma operation.
  246.      * At this point the current dma buffer is full and we can
  247.      * write it to disk. We set the "available data" pointer
  248.      * to point to the just-filled buffer, and start the next dma
  249.      * operation on the other buffer. At the conclusion of
  250.      * operations, we output a non-specific end-of-interrupt
  251.      * to the interrupt controller.
  252.      *
  253.      * The PC bus provides no mechanism for "unlatching" an
  254.      * interrupt request once it has been serviced. In order to
  255.      * enable the next interrupt, the hardware must be designed
  256.      * so that the request can be reset, by a write to an i/o
  257.      * port, for example. The external routine reset_irq()
  258.      * must be coded to perform this function.
  259.      *
  260.      * Declaring this routine as type 'interrupt', ensures
  261.      * that all registers are saved, the C data segment is set
  262.      * correctly, and that the routine returns with an IRET
  263.      * instruction. Further interrupts are disabled during the
  264.      * execution of this routine.
  265.      */
  266.  
  267.     curr_buf = dma_buffers[buf_index];   /* post just-filled buf address */
  268.     buf_index ^= 1;                 /* index next buffer */
  269.     start_dma(dma_buffers[buf_index],    /* start dma on next buffer */
  270.                 buf_size);
  271.     if( file_handle )              /* if disk is enabled.. */
  272.         write_buf();             /* write buffer to disk */
  273.     reset_irq();                 /* do hardware-specific reset */
  274.     outp(INTA00, EOI);             /* signal end of int */
  275. }
  276.  
  277. /*--------------------------------------------------------------------------*/
  278. static int write_buf()            /* write buffer to disk file */
  279. {
  280.     if( *dos_crit_addr )        /* first check dos critical section flag */
  281.     {
  282.         lost_buffers++;        /* ..if set, skip writing this buffer */
  283.         return 0;        /* ..not really an error in this case */
  284.     }
  285.  
  286.     r.x.dx = FP_OFF(curr_buf);  /* ok to write now, set address in */
  287.     s.ds   = FP_SEG(curr_buf);  /*   proper registers for dos call */
  288.     r.x.bx = file_handle;        /* set file handle to write to */
  289.     r.x.cx = buf_size;        /* set byte count for write */
  290.                     /* WARNING - can't write 64K! */
  291.     r.h.ah = 0x40;            /* dos write-to-file-handle function */
  292.     if(intdosx(&r, &r, &s) == buf_size  /* check return value and..  */
  293.             && r.x.cflag == 0)  /* ..carry flag for success code */
  294.         return 0;            /* return success */
  295.     else
  296.     {
  297.         lost_buffers++;         /* didn't write this buffer */
  298.         return 1;         /* return failure */
  299.     }
  300. }
  301.  
  302. [LISTING TWO]
  303.  
  304. /*--------------------------------------------------------------------------*/
  305. /* test.c -- test dma data acquisition
  306.  * Compiler: Microsoft C Version 5.0
  307.  * Must compile with -Gs option because
  308.  * reset_irq() is called from interrupt.
  309.  * Tom Nolan - 8/7/89
  310.  */
  311.  
  312. #include <bios.h>
  313. int far *dma_buffers[2];        /* pointers to two buffers */
  314. int far *curr_buf;            /* pointer to current buffer */
  315. int buf_size;                /* buffer size */
  316. int buf_index;                /* index of current buffer */
  317. int dma_irq = 3;            /* hardware int request line */
  318. int dma_chan = 1;            /* hardware dma channel number */
  319. int file_handle = 0;            /* file handle */
  320. int lost_buffers = 0;            /* write errors */
  321.  
  322. /* In this program, each dma buffer will be filled with
  323.  * NUMFR "frames", each of size FRSIZE (in words). The second
  324.  * word of each frame is a frame counter, which increments 
  325.  * modulo 256. The program checks the frame counters to make
  326.  * sure they are sequential and no data was lost.
  327.  */
  328.  
  329. #define FRSIZE     64                /* words per frame */
  330. #define NUMFR    8                /* frames per dma buffer */
  331.  
  332. /*--------------------------------------------------------------------------*/
  333. main()
  334. {
  335.     int temp;
  336.     int i;
  337.     unsigned char frame;
  338.     int far *cp;
  339.  
  340.     reset_irq();                 /* clear interrupt request */
  341.     buf_size = FRSIZE * NUMFR * sizeof(int); /* figure out buffer size */
  342.     alloc_dma_buf();             /* allocate buffers */
  343.     printf("buf1 = %p buf2 = %p\n",         /* informational output */
  344.         dma_buffers[0], dma_buffers[1]);
  345.     dma_setup();                   /* set up for dma operations */
  346.  
  347.     outp(0x3a0,0);                   /* reset fifo on hw interface */
  348.     start_dma(dma_buffers[0], buf_size);   /* start up the data acq */
  349.     file_handle = creat("tmp.dat");           /* open a file for raw data */
  350.     temp = buf_index;
  351.  
  352.     while( !_bios_keybrd(_KEYBRD_READY) )    /* quit on next keystroke */
  353.     {
  354.         if(temp != buf_index)        /* wait for dma complete */
  355.         {
  356.             printf("%d: ",temp);    /* print buffer index */
  357.  
  358.             for(i=0, cp = curr_buf+1; i<NUMFR; i++, cp += FRSIZE)
  359.             {
  360.                   if(frame != *cp) printf("*"); /* frame counter bad */
  361.                   else printf(" ");        /* frame counter good */
  362.                   printf("%04x", *cp);    /* print frame counter */
  363.                   frame = *cp + 1;/* next expected counter value */
  364.             }
  365.         printf(" : %d\n",lost_buffers);    /* keep track of lost writes */
  366.         temp = buf_index;          /* next expected buffer number */
  367.           }
  368.     }
  369.     close(file_handle);            /* close data file */
  370.     exit(0);                /* halt dma and exit */
  371. }
  372.  
  373. /*--------------------------------------------------------------------------*/
  374. reset_irq()            /* clear interrupt request in hardware */
  375. {
  376.     inp(0x3A0);
  377. }
  378.